Click on the example application names below to look at the source code:
#include// 02.04.19 - WHISKER: Debounce Pulled Up Switch Input. // // This is a two wire interface using a 74LS174 as a shift // register and the code called from the mainline (and timed // from TMR0 interrupt) // // // Hardware Notes: // PIC16F827 Running at 4 MHz with External Oscillator // RB0 - Pulled Up Switch Input (NOT Using Internal PORTB Pull Ups) // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte char Message1[2] = "\f"; // Clear Display char Message2[12] = "\fdebouncing"; // Display "debouncing" char Message3[9] = "\fPRESSED"; // Display "PRESSED" volatile char ButtonPress = 0; // Count of the Button Presses // Configuration Fuses #if defined (_16F84) #warning PIC16F84 selected __CONFIG(0x03FF1); // PIC16F84 Configuration Fuses: // - XT Oscillator // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off #elif defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // Increment Counter if Button Pressed if (!RB0) // Button Pressed if (ButtonPress < 20) // Button Press being debounced ButtonPress++; else; // Button Pressed for 20 msecs else // Button Released if (ButtonPress != 0) // Button Release being debounced ButtonPress--; else; // Button released for more than 20 msecs // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: // LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: LCDByte(0x00E, 0); // Step 10 - Display On LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here } // End Interrupt Handler // Mainline void main(void) // Template Mainline { OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts LCDInit(); // Initialize the LCD Port // Put in Interface initialization code here while (1 == 1) { // Loop forever // Put in Robot high level operation code here switch(ButtonPress) { // Process Button Press case 0: // Released - Fully Debounced LCDOut(Message1); break; case 20: // Pressed - Fully Debounced LCDOut(Message3); break; default: // Between 0 and 20 - Button being debounced LCDOut(Message2); break; } // endswitch } // endwhile } // End of Mainline
Infra-red object detection may seem to be somewhat esoteric for use with simple hobby robots, but they actually work very well and provide the advantage over whiskers that objects can be detected before the robot collides with them and is potentially damaged.
The source code \code\irdetect\irdetect.c file on the CD-ROM is:
#include// 02.04.17 - IRDETECT: Sense Objects in front of IR LED/IR Detector // // This is a two wire interface using a 74LS174 as a shift // register and the code called from the mainline (and timed // from TMR0 interrupt) // // // Hardware Notes: // PIC16F827 Running at 4 MHz with External Oscillator // RB0 - IR Detector Input // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // RB3 - PWM Output // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte char Message1[2] = "\f"; // Clear Display char Message2[11] = "\fCOLLISION"; // Display "Collision" volatile char Collision = 0; // Current Collision volatile char OldCollision = 0; // Configuration Fuses #if defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // Output a Series of Pulses for Collision Detection TRISB3 = 0; // Enable PWM Output while (TMR0 < 64); // For a Limited Number of Cycles if (!RB0) // If Low, then Collision Collision = 1; else Collision = 0; TRISB3 = 1; // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: // LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: LCDByte(0x00E, 0); // Step 10 - Display On LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here } // End Interrupt Handler // Mainline void main(void) // Template Mainline { OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts LCDInit(); // Initialize the LCD Port // Put in Interface initialization code here CCPR1L = 13; // 50% Duty Cycle CCP1CON = 0b000001111; // Turn on PWM Mode PR2 = 26; // 26 usec Cycle for 38 KHz TMR2 = 0; // Reset TMR2 T2CON = 0b000000100; // TMR2 is On, Scalers 1:1 while (1 == 1) { // Loop forever // Put in Robot high level operation code here if (Collision != OldCollision) { OldCollision = Collision; // Save Current Collision State if (Collision) // Put in Appropriate Message LCDOut(Message2); else LCDOut(Message1); while (LCDState); // Wait for LCD Available } // endif } // endwhile } // End of Mainline
I find infra-red TV remote controls to be very useful when working with robots - they will allow you to control the robot directly very simply and generally quite inexpensively. The \code\remote\remote.c application will display the bit pattern received from "Sony" brand TV remote controls. The \code\remote\remote.wat file was created to help me debug the application.
The source code for \code\remote\remote.c is presented below:
#include// 02.04.17 - Updated for New Compiler Format // 02.02.15 - Remote: Display Sony IR Commands on an LCD // // Take Input from Sony IR Remote Control and Display on LCD. // // To display data, two wire interface using a 74LS174 as a shift // register is used and processed by the biologic code into a // string displayed by the LCD. // // Data Stream: // // Leader/Header High "1" "0" // -------+ +----+ +----+ +------------ // | | | | | | // +-----------------+ +-----+ +------------+ // // | 2.4 msecs |540u|660us|540u| 1.2 msecs | // // |1200 msecs| 1.76 msecs | // // | 300 | 440 | // // // Hardware Notes: // PIC16F84/PIC16F627 Running at 4 MHz with an external ceramic resonator // RB0 - Sony IR Input, Use Falling Interrupt // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte unsigned int DataIn; // Data Input unsigned char DataInCount = 0; // Number of Characters to Read In unsigned char DataReady = 0; // Data to be Read in int SaveRTC; // Saved RTC/TMR0 for Interrupt Pins int CurrentRTC; char Message[20] = "\f-> 0b0xxxxxxxxxxxx"; // Header of Incoming Bits // Configuration Fuses #if defined (_16F84) #warning PIC16F84 selected __CONFIG(0x03FF1); // PIC16F84 Configuration Fuses: // - XT Oscillator // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off #elif defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState != 0); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // Check for Packet which was lost and stop waiting unless packet is active CurrentRTC = (RTC & 0x0FF) - ((SaveRTC >> 8) & 0x0FF); if (CurrentRTC < 0) // Calculate Time Since Save CurrentRTC = 0 - CurrentRTC; if ((DataInCount != 0) && (CurrentRTC > 9)) DataInCount = 0; // Reset and Wait for the Next Character // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: LCDByte(0x00E, 0); // Step 10 - Display On LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here if (INTF) { // RB0/INT Pin Interrupt if (DataReady) // Waiting to Process Previous Packet ; // Ignore else if (DataInCount == 0) { // New Message Coming In DataInCount = 12; // 12 Bits to Come in SaveRTC = ((RTC & 0x0FF) << 8) + TMR0; // Save the Current Time DataIn = 0; } else { // Bit to Process CurrentRTC = ((RTC & 0x0FF) << 8) + TMR0; // Get the Current Time if ((SaveRTC = CurrentRTC - SaveRTC) < 0) SaveRTC = 0 - SaveRTC; if ((SaveRTC > 250) && (SaveRTC < 350)) { DataIn = (DataIn << 1) + 1; // "1" Received if (--DataInCount == 0) DataReady = 1; } else if ((SaveRTC > 390) && (SaveRTC < 490)) { DataIn = DataIn << 1; // "0" Received if (--DataInCount == 0) DataReady = 1; } else // Invalid - Delete DataInCount = 0; SaveRTC = CurrentRTC; // Save the Execution Time } // endif INTF = 0; // Reset Interrupt } // endif } // End Interrupt Handler // Mainline void main(void) // Template Mainline { int i; OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts // Put in Interface initialization code here LCDInit(); // Initialize the LCD Port INTEDG = 1; // Interrupt on Rising Edge of RB0/IR TX'r INTE = 1; // Enable RBO/INT Pin Interrupts while (1 == 1) { // Loop forever // Put in Robot high level operation code here if (DataReady) { while (LCDState != 0); // Wait for the LCD Available for (i = 0; i < 12; i++) { if (DataIn & (1 << 11)) Message[i + 7] = '1'; else Message[i + 7] = '0'; DataIn = DataIn << 1; } // endfor LCDOut(Message); // Pass String to Output DataReady = 0; // Reset the Data Flag } // endif } // endwhile } // End of Mainline
With an IR receiver built into the robot for collision detection or for remote control, it is natural to want to combine these functions. This application, found in \code\combine\combine.c, filters the incoming signals and determines whether or not there is an object in front of the robot or if a command from a remote control is coming in.
#include// 02.04.19 - Created from "irdetect" and "remote" // 02.02.15 - Remote: Display Sony IR Commands on an LCD // // A 38 kHz IR Signal to be used as an object detection system // // Take Input from Sony IR Remote Control and Display on LCD. // // To display data, two wire interface using a 74LS174 as a shift // register is used and processed by the biologic code into a // string displayed by the LCD. // // Data Stream: // // Leader/Header High "1" "0" // -------+ +----+ +----+ +------------ // | | | | | | // +-----------------+ +-----+ +------------+ // // | 2.4 msecs |540u|660us|540u| 1.2 msecs | // // |1200 msecs| 1.76 msecs | // // | 300 | 440 | // // // Hardware Notes: // PIC16F627 Running at 4 MHz with an external ceramic resonator // RB0 - Sony IR Input, Use Falling Interrupt // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte unsigned int DataIn; // Data Input unsigned char DataInCount = 0; // Number of Characters to Read In unsigned char DataReady = 0; // Data to be Read in int SaveRTC; // Saved RTC/TMR0 for Interrupt Pins int CurrentRTC; char Message[22] = "\376\002-> 0b0xxxxxxxxxxxx!"; // Incoming Bit Display char Message1[12] = "\376\300 "; // Clear Display char Message2[12] = "\376\300COLLISION"; // Display "Collision" volatile char Collision = 0; // Current Collision volatile char OldCollision = 0; // Configuration Fuses #if defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState != 0); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // Check for Packet which was lost and stop waiting unless packet is active if (DataInCount != 0) { // Check/Update Time for New IR Packet Coming In if ((CurrentRTC = (RTC & 0x0FF) - ((SaveRTC >> 8) & 0x0FF)) < 0) CurrentRTC = 0 - CurrentRTC; // Calculate Time Since Save if (CurrentRTC > 9) { // Delay too Long? DataInCount = 0; // Reset and Wait for the Next Packet INTE = 0; // Disable Incoming Data Interrupts } // endif } else if ((!RB0) && (!DataReady)) { // Data Packet Coming in? DataInCount = 13; // 12 Bits to Come in/13 Edges SaveRTC = ((RTC & 0x0FF) << 8) + TMR0; // Save the Current Time INTF = 0; // Enable Pin Change Interrupt INTE = 1; } else { // Do Collision Detection TRISB3 = 0; // Enable PWM Output while (TMR0 < 64); // For a Limited Number of Cycles if (!RB0) // If Low, then Collision Collision++; // Must Have 3 Collisions in a Row else Collision = 0; TRISB3 = 1; } // endif // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: LCDByte(0x00E, 0); // Step 10 - Display On LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here if (INTF) { // RB0/INT Pin Interrupt CurrentRTC = ((RTC & 0x0FF) << 8) + TMR0; // Get the Current Time if ((SaveRTC = CurrentRTC - SaveRTC) < 0) SaveRTC = 0 - SaveRTC; if ((SaveRTC > 250) && (SaveRTC < 350)) { DataIn = (DataIn << 1) + 1; // "1" Received if (--DataInCount == 0) { DataReady = 1; INTE = 0; // Finished, Disable Interrupts } // endif } else if ((SaveRTC > 390) && (SaveRTC < 490)) { DataIn = DataIn << 1; // "0" Received if (--DataInCount == 0) { DataReady = 1; INTE = 0; // Finished, Disable Interrupts } // endif } else if (--DataInCount != 12) { // Invalid - Delete DataInCount = 0; INTE = 0; // Finished, Disable Interrupts } // endif SaveRTC = CurrentRTC; // Save the Execution Time INTF = 0; // Reset Interrupt } // endif } // End Interrupt Handler // Mainline void main(void) // Template Mainline { int i; OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts // Put in Interface initialization code here LCDInit(); // Initialize the LCD Port INTEDG = 1; // Interrupt on Rising Edge of RB0/INT IR TX'r CCPR1L = 13; // 50% Duty Cycle CCP1CON = 0b000001111; // Turn on PWM Mode PR2 = 26; // 26 usec Cycle for 38 KHz TMR2 = 0; // Reset TMR2 T2CON = 0b000000100; // TMR2 is On, Scalers 1:1 while (1 == 1) { // Loop forever // Put in Robot high level operation code here if (DataReady) { while (LCDState != 0); // Wait for the LCD Available for (i = 0; i < 12; i++) { if (DataIn & (1 << 11)) Message[i + 8] = '1'; else Message[i + 8] = '0'; DataIn = DataIn << 1; } // endfor LCDOut(Message); // Pass String to Output DataReady = 0; // Reset the Data Flag } // endif if ((Collision == 0) && (OldCollision == 3)) { OldCollision = Collision; // Save Current Collision State LCDOut(Message1); } // endif if ((Collision == 3) && (OldCollision == 0)) { OldCollision = Collision; // Save Current Collision State LCDOut(Message2); } // endif } // endwhile } // End of Mainline
IR object detection can determine the distance to an object to some degree, but for reasonable precision (ie down to an inch or less), another method, like ultrasonic ranging is required. There are a lot of different ultrasonic ranging modules available, with the most popular being the Polaroid 6500.
\code\ultra\ultra.c interfaces with a Polaroid 6500 to determine the distance from the 6500's transducer to an object. To debug the application, I came up with the MPLAB IDE stimulus file \code\ultra\ultra.sti. The source code for the example application is:
#include// 02.04.19 - ULTRA: Get the distance from the Polaroid 6500 // Transducer and Display it on a LCD // // This is a two wire interface using a 74LS174 as a shift // register and the code called from the mainline (and timed // from TMR0 interrupt) // // // Hardware Notes: // PIC16F827 Running at 4 MHz with External Oscillator // RB0 - Pulled Up "ECHO" from Polaroid 6500 // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // RB3 - "INIT" Line to start the distance measurement // // Global Variables unsigned int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte char Message[9] = "\fxx\' xx\""; // Distance Measurement char Message2[9] = "\fInvalid"; // "Invalid" Distance Measurement Message unsigned int CheckDlay = 50; // Wait 500 msecs between Range Findings unsigned int PulseStartRTC; // RTC at Pulse Send unsigned int PulseEndRTC; // RTC at Pulse End unsigned char PulseEndTMR0; // TMR0 Value for Pulse End unsigned char PulseState = 0; // 0 - Waiting to Check Distance // 1 - Distance Measurement Pending // 2 - Have Distance // 3 - Invalid Distance unsigned long PulseTime; // Number of msecs for the Pulse Time unsigned long PulseInches; // Number of Inches measured unsigned long PulseFeet; // Number of Feet measured // Configuration Fuses #if defined (_16F84) #warning PIC16F84 selected __CONFIG(0x03FF1); // PIC16F84 Configuration Fuses: // - XT Oscillator // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off #elif defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // Ultra sonic operations if (--CheckDlay == 0) { // Start the Pulse Output PulseStartRTC = RTC; PulseState = 1; // Doing Distance Measurement RB3 = 1; // Start Pulse INTF = 0; // Make sure no Pending Interrupts INTE = 1; // Enable RB0/INTF CheckDlay = 500; // Wait another 1/2 Second if (RTC > 64900) // Make sure no issue with Crossing RTC Roll Over CheckDlay += 200; } // endif if ((PulseState == 1) && ((PulseStartRTC + 60) < RTC)) { RB3 = 0; // Invalid delay, Turn off INTF = 0; // 6500 and requests INTE = 0; PulseState = 3; // Invalid } // endif // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: // LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: LCDByte(0x00E, 0); // Step 10 - Display On LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here if (INTF) { PulseEndTMR0 = TMR0; // Recored the Pulse End PulseEndRTC = RTC; RB3 = 0; // Reset Measurement Request PulseState = 2; // Have the Pulse INTF = 0; // Turn Off Interrupts INTE = 0; } // endif } // End Interrupt Handler // Mainline void main(void) // Template Mainline { unsigned int temp; // Temporary Storage Value OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts LCDInit(); // Initialize the LCD Port // Put in Interface initialization code here RB3 = 0; // Enable RB3 for Output TRISB3 = 0; // Keep Low Initially while (1 == 1) { // Loop forever // Put in Robot high level operation code here switch(PulseState) { // Process according to the Pulse State case 2: // Have Distance, Print Measurement PulseTime = (((long) PulseEndRTC * 256) + (long) PulseEndTMR0) * 40; PulseTime -= ((long) PulseStartRTC * (256 * 40)); PulseInches = PulseTime / 1533; PulseFeet = PulseInches / 12; PulseInches = PulseInches % 12; if ((temp = PulseFeet / 10) == 0) Message[1] = ' '; // Distance Not 10s of feet else Message[1] = temp + '0'; Message[2] = (PulseFeet % 10) + '0'; if ((temp = PulseInches / 10) == 0) Message[5] = ' '; else Message[5] = temp + '0'; Message[6] = (PulseInches % 10) + '0'; LCDOut(Message); PulseState = 0; // Wait for the Next Pulse break; case 3: // Invalid for some reason LCDOut(Message2); PulseState = 0; // Wait for the Next Pulse break; } // endswitch } // endwhile } // End of Mainline
I will demonstrate two ways in which light can be detected in a robot. The first method, found in \code\light\light1.c wires two CDS cells as a voltage divider and responds to which ever device has the lowest resistance. The lower resistance is assumed to indicate that CDS cell is being exposed to more light. The source code for the application is listed below:
#include// 02.04.24 - LIGHT: Look at two CDS Cells in Series and indicate // brighter one // // This is a two wire interface using a 74LS174 as a shift // register and the code called from the mainline (and timed // from TMR0 interrupt) // // // Hardware Notes: // PIC16F827 Running at 4 MHz with External Oscillator // RA0 - Comparator2, Connected to a 2 CDS Cell Voltage Divider // with "Left" on Top // RA1 - Comparator1, Tied to Vss // RA2 - 1/2 Vdd from Vref module, output to check Vref operation // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte char MessageL[7] = "\376\200*\376\220 "; // Put "*" on Left // 1 2 34 5 67 char MessageR[7] = "\376\200 \376\220*"; // Put "*" on Left // Configuration Fuses #if defined(_16F627) // #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut int ADCPoll() { return C2OUT; } // End ADCPoll // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: // LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: // LCDByte(0x00E, 0); // Step 10 - Display On LCDByte(0x00C, 0); // Step 10 - Display On/No Cursor LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here } // End Interrupt Handler // Mainline void main(void) // Template Mainline { OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts // Put in Interface initialization code here LCDInit(); // Initialize the LCD Port CMCON = 0x002; // Enable the Comparator Module // C1/C2 Normal, not inverted // CIS = 0 // Mode 2, compare against Vdd TRISA = 0x007; // Just bits RA0 and RA1 are inputs VRCON = 0x0EC; // Enable Vref module // Vref output on RA2 // High Vref Range // Ladder value of 12 while (1 == 1) { // Loop forever // Put in Robot high level operation code here if (!ADCPoll()) // Bit 7 of CMCOM LCDOut(MessageL); // Light is left else LCDOut(MessageR); // Light is right } // endwhile } // End of Mainline
\code\light\light2.c differs from the previous application by the way it compares the resistance of two CDS cells. \code\light\light2.c calculates a numeric value for each of the CDS cell's resistance. In a robot, this numeric value would be compared. The source code for the application is:
#include// 02.04.24 - LIGHT2: Implement a dual RC Network ADC on the // PIC16F84/PIC16F627. 10K CDS Cell with a 0.01 uF Capacitor // // This is a two wire interface using a 74LS174 as a shift // register and the code called from the mainline (and timed // from TMR0 interrupt) // // // Hardware Notes: // PIC16F827 Running at 4 MHz with External Oscillator // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // RB4 - Left ADC // RB5 - Right ADC // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte char MessageL[19] = "\376\200 *"; // Move Cursor to Start with bar Graph // 1 2 34567890123456789 char MessageR[19] = "\376\300 *"; // Move Cursor to 2nd Line with bar Graph volatile char ADCState = 0; // Current ADC State Machine Operating State volatile char ADCDlay = 1; // Delay 10 msecs between each ADC Operation volatile char LeftADC = 0; // Last Left ADC Value volatile char RightADC = 0; // Last Right ADC Value // Configuration Fuses #if defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: // LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: // LCDByte(0x00E, 0); // Step 10 - Display On LCDByte(0x00C, 0); // Step 10 - Cursor Off LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch // ADC State Machine Read Here if (!LCDState) { // Only Execute if LCD NOT Active switch (ADCState) { case 0: // ADC Start Delay if (--ADCDlay == 0) ADCState++; break; case 1: // Start Charging Left and Right (for 1 msec min.) TRISB4 = 0; TRISB5 = 0; RB4 = 1; RB5 = 1; ADCState++; // Jump to next state in 1 msec break; case 2: TRISB4 = 1; // Turn off Left Pin Driver temp = PORTB; // Clear pending Interrupts on Port Change RBIF = 0; RBIE = 1; ADCState++; break; case 3: // Roll Over, No Interrupt LeftADC = 0x0FF; temp = PORTB; // Clear pending Interrupts on Port Change RBIF = 0; RBIE = 0; ADCState++; break; case 4: // Start Charging Right (for 1 msec) TRISB5 = 1; // Change TRISB to Input temp = PORTB; // Clear pending Interrupts on Port Change RBIF = 0; RBIE = 1; ADCState++; break; case 5: // Roll Over, No Interrupt RightADC = 0x0FF; temp = PORTB; // Clear pending Interrupts on Port Change RBIF = 0; RBIE = 0; ADCDlay = 10; ADCState = 0; break; } // endswitch } // endif } // endif // Put Different Interrupt Handlers here if (RBIF) { // Interrupt on Pin Change switch(ADCState) { case 3: // Left ADC Active LeftADC = TMR0; // Get the ADC Count ADCState++; break; case 5: // Right ADC Active RightADC = TMR0; // Get the ADC Count ADCDlay = 10; ADCState = 0; break; } // endswitch temp = PORTB; // Clear pending Interrupts on Port Change RBIF = 0; RBIE = 0; } // endif } // End Interrupt Handler // Mainline void main(void) // Template Mainline { unsigned int i, j; unsigned int tempLeft, tempRight; OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts // Put in Interface initialization code here LCDInit(); // Initialize the LCD Port while (1 == 1) { // Loop forever // Put in Robot high level operation code here while (ADCState != 2); // Wait for ADC Operation to Start while (ADCState); // Wait for ADC Operation to Complete tempLeft = LeftADC; tempRight = RightADC; // Save ADC Values j = (tempLeft / 8) + 1; // Calculate the number of Splats for Left for (i = 2; i < 18; i++ ) if ((i - 2) <= j) // Print a Splat MessageL[i] = '*'; else // Print a Blank MessageL[i] = ' '; j = (tempRight / 8) + 1; // Calculate the number of Splats for Right for (i = 2; i < 18; i++ ) if ((i - 2) <= j) // Print a Splat MessageR[i] = '*'; else // Print a Blank MessageR[i] = ' '; LCDOut(MessageL); // Left Graph LCDOut(MessageR); // Right Graph } // endwhile } // End of Mainline
The last method that a robot can use for sensing its outside environment is to listen for sounds. \code\sound\sound.c will indicate when a microphone receives a loud sound which has been filtered to its basic components. For debugging, I created \code\sound\sound.wat. The source code for the application is:
#include// 02.01.27 - Sound: Display Message when sound received // (and finished) on LCD // // The sound input is filtered and amplified using a 324 // op-amp. To remove small transients, the application // Checks the TMR1 count once every 20 msecs and compares // the current value to see if it has increased by 5 over // the previous 20 msec poll. // // This is a two wire interface using a 74LS174 as a shift // register and the code called from the mainline (and timed // from TMR0 interrupt) // // // Hardware Notes: // PIC16F84 Running at 4 MHz // RB1 - CLock Signal to the LCD // RB2 - Data Signal to the LCD // // Global Variables int RTC = 0; // Real Time Clock Counter volatile char LCDDlay = 20; // Initialization Delay Value volatile char LCDState = 1; // Current LCD State static volatile bit Clock @ (unsigned)&PORTB*8+1; static volatile bit ClockTRIS @ (unsigned)&TRISB*8+1; static volatile bit Data @ (unsigned)&PORTB*8+2; static volatile bit DataTRIS @ (unsigned)&TRISB*8+2; char * MessageOut; // Message to be Sent Out volatile char MessageOuti = 0; // Index to current Message Byte char Message1[18] = "\376\200sound - 0x0xxxx"; // Message when sound // 1 2 3456789012345678 char Message2[18] = "\376\200 "; // Message when nothing char soundCounter = 20; // Wait 20 msecs for Sound Check int OldTMR1 = 0; // Timer Values int CurrentTMR1; volatile char soundState = 0; // Sound State Variable // 0 - No Sound // 1 - Sound Received, Check Next // 2 - Sound Received, Ended // Configuration Fuses #if defined(_16F627) #warning PIC16F627 with external XT oscillator selected __CONFIG(0x03F61); // PIC116F627 Configuration Fuses: // - External "XT" Oscillator // - RA6/RA7 Digital I/O // - External Reset // - 70 msecs Power Up Timer On // - Watchdog Timer Off // - Code Protection Off // - BODEN Enabled #else #error Unsupported PICmicro MCU selected #endif // Subroutines char GetHex(char Value) { char returnValue; if (Value > 9) returnValue = Value + 'A' - 10; else returnValue = Value + '0'; return returnValue; } // End GetHex Dlay(int msecs) // Delay Specified Number of msecs { volatile int variableDlay; // 1 Second Delay Variable variableDlay = RTC + msecs + 1; // Get the End Time while (variableDlay != RTC); } // End Dlay LCDNybble(char Nybble, char RS) { // Send Nybble to LCD unsigned int i; Data = 0; // Clear the '174 for (i = 0; i < 6; i++) { // Repeat for six bits Clock = 1; Clock = 0; // Write the "0"s into the '174 } // endfor Data = 1; // Output the "AND" Value Clock = 1; Clock = 0; Data = RS; // Output the RS Bit Value Clock = 1; Clock = 0; for (i = 0; i < 4; i++) { // Output the Nybble if ((Nybble & 0x008) != 0) Data = 1; // Output the High Order Bit else Data = 0; Clock = 1; Clock = 0; // Strobe the Clock Nybble = Nybble << 1; // Shift up Nybble for Next Byte } // endfor Data = 1; Data = 0; // Toggle the "E" Clock Bit } // End LCDNybble LCDByte(char Byte, char RS) { // Send Byteto LCD LCDNybble((Byte >> 4) & 0x00F, RS); // Send High Nybble LCDNybble(Byte & 0x00F, RS); // Send Low Nybble } // End LCDByte LCDInit() // Initialize the LCD I/O Pins { Clock = 0; Data = 0; // Low I/O Bits ClockTRIS = 0; DataTRIS = 0; } // End LCDInit LCDOut(char * const LCDString) // Output the Data String { while (LCDState); // Wait for LCD Available MessageOut = LCDString; // Load up the string LCDState = 100; // Start Sending the String } // End LCDOut // Interrupt Handler void interrupt tmr0_int(void) // TMR0 Interrupt Handler { char temp; int TMR1Value; if (T0IF) { T0IF = 0; // Reset Interrupt Flag RTC++; // Increment the Clock // Put additional interface code for 1 msec interrupt here // Sound Check Software if (soundState == 0) // Check the Current Sound Value if (--soundCounter == 0) { // Time to Check? soundCounter = 20; // Reset TMR1Value = (TMR1H * 0x0100) + TMR1L; if (TMR1Value >= (OldTMR1 + 2)) soundState = 1; // Indicate Sound Received OldTMR1 = TMR1Value; // Save Current Counter } else; else if (soundState == 1) // Check to see if Sound still active if (--soundCounter == 0) { // Time to Check? soundCounter = 20; // Reset TMR1Value = (TMR1H * 0x0100) + TMR1L; if (TMR1Value < (OldTMR1 + 2)) soundState = 2; // Indicate Sound Finished OldTMR1 = TMR1Value; // Save Current Counter } else; // LCD State Machine switch(LCDState) { // Process the State Machine information case 1: // Start LCD Initialization Process if (--LCDDlay == 0) LCDState++; break; // Wait 20 msecs for LCD to reset case 2: // LCDNybble(0x003, 0); // Step 2 - Send Init Char LCDDlay = 5; LCDState++; case 3: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 4: LCDNybble(0x003, 0); // Step 3 - Send Init Char LCDState++; break; case 5: LCDNybble(0x003, 0); // Step 4 - Send Init Char LCDState++; break; case 6: LCDNybble(0x002, 0); // Step 5 - Set Operating LCDState++; // Interface Size (4 Bits) break; case 7: LCDByte(0x028, 0); // Step 6 - Set Operating LCDState++; break; case 8: LCDByte(0x008, 0); // Step 7 - Display Off LCDState++; break; case 9: LCDByte(0x001, 0); // Step 8 - Clear Display LCDState++; LCDDlay = 5; // Wait 5 msecs for Clear to Complete break; case 10: // Wait for Command to Complete if (--LCDDlay == 0) LCDState++; break; case 11: LCDByte(0x006, 0); // Step 9 - Shift LCDState++; break; case 12: LCDByte(0x00E, 0); // Step 10 - Display On LCDState = 0; // Ready to Run break; case 100: // Output a Character in "MessageOut" switch (temp = MessageOut[MessageOuti++]) { case '\0': // At the End of the Message LCDState = 0; MessageOuti = 0; // Reset Message Index break; case '\f': // Clear Display LCDByte(0x001, 0); LCDState++; LCDDlay = 5; break; case 254: // Command Byte follows if ((temp = MessageOut[MessageOuti++]) == 0) LCDState = 0; else { if (temp < 4) { LCDState++; LCDDlay = 5; } // endif LCDByte(temp, 0); } // endif break; default: // All other characters LCDByte(temp, 1); } // endswitch break; case 101: // Message Delay if (--LCDDlay == 0) LCDState--; break; } // endswitch } // endif // Put Different Interrupt Handlers here } // End Interrupt Handler // Mainline void main(void) // Template Mainline { OPTION = 0x0D1; // Assign Prescaler to TMR0 // Prescaler is /4 TMR0 = 0; // Reset the Timer for Start T0IE = 1; // Enable Timer Interrupts GIE = 1; // Enable Interrupts LCDInit(); // Initialize the LCD Port // Put in Interface initialization code here TMR1H = TMR1L = 0; // Initialize Timer 1 T1CON = 0x003; // 1:1 Prescaler // T1OSCEN Off // _T1SYNC Active // TMR1CS is Exteral Clock // TMR1ON while (1 == 1) { // Loop forever // Put in Robot high level operation code here while (soundState != 2); // Wait for Sound Received CurrentTMR1 = (TMR1H * 0x0100) + TMR1L; Message1[13] = GetHex((CurrentTMR1 & 0x0F000) >> 12); Message1[14] = GetHex((CurrentTMR1 & 0x0F00) >> 8); Message1[15] = GetHex((CurrentTMR1 & 0x0F0) >> 4); Message1[16] = GetHex(CurrentTMR1 & 0x0F); LCDOut(Message1); // Indicate "Sound" Dlay(1000); // Wait 1 Second LCDOut(Message2); // Clear the Sound Message soundState = 0; // Reset and wait for next sound } // endwhile } // End of Mainline